package com.googlecode.imanager.core.base;

import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

import com.googlecode.imanager.client.Application;
import com.googlecode.imanager.client.Control;
import com.googlecode.imanager.client.exception.BridgeException;
import com.googlecode.imanager.client.style.Theme;
import com.googlecode.imanager.client.style.listener.StyleAttribute;
import com.googlecode.imanager.client.style.listener.StyleWrapper;
import com.googlecode.imanager.core.base.style.ComputedTheme;

/**
 * @author wellsantos@wellsantos.com
 * @since 0.01
 */
public abstract class AbstractApplication extends BridgeApplication {

    private final Map<Class<? extends Control>, Constructor<?>> controlMap;
    
    private final Application application;
    private final ComputedTheme computedTheme;

    public AbstractApplication(DefaultManager manager, Application application, OutputStream output) {
        super(manager, application, output);
        this.application = application;
        controlMap = new HashMap<Class<? extends Control>, Constructor<?>>();
        computedTheme = new ComputedTheme(application);
        initialize();
    }
    
    public Application getApplication() {
        return application;
    }

    protected abstract void initialize();
    
    public ComputedTheme getComputedTheme(){
        return computedTheme;
    }

    @Override
    protected void start() {
        super.start();
        computedTheme.onThemeAttach(application.getTheme());
    }

    @Override
    protected void destroy() {
        computedTheme.destroy();
        super.destroy();
    }

    @Override
    public boolean isFireStyleChangesToChildren() {
        return true;
    }

    @Override
    public void onThemeAttach(Theme theme) {
        computedTheme.onThemeAttach(theme);
    }

    @Override
    public void onThemeDetach(Theme theme) {
        computedTheme.onThemeDetach(theme);
    }

    @Override
    public <T> void onStyleAttributeChanged(StyleWrapper styleWrapper,
            StyleAttribute<T> attribute, T attributeValue) {
        computedTheme.onStyleAttributeChanged(styleWrapper, attribute, attributeValue);
    }

    protected void registerImplType(Class<? extends Control> srcType, Class<? extends AbstractControl> implType){
        Constructor<?> constructor = null;
        
        for (Constructor<?> c : implType.getConstructors()){
            Class<?>[] args = c.getParameterTypes();
            
            if (args.length == 2
                    && BridgeApplication.class.isAssignableFrom(args[0])
                    && Control.class.isAssignableFrom(args[1])){
                constructor = c;
            }
        }
        
        if (constructor == null){
            try {
                constructor = implType.getConstructor(BridgeApplication.class, Control.class);
            } catch (Exception e) {
                throw new BridgeException(e);
            }
        }
        
        controlMap.put(srcType, constructor);
    }
    
    @SuppressWarnings("unchecked")
    protected void registerImplTypeTree(Class<? extends Control> srcType, Class<? extends AbstractControl> implType){
        Class<?> type = srcType; 
        registerImplType(srcType, implType);
        
        while (type != Control.class){
            type = type.getSuperclass();
            
            if (!controlMap.containsKey(type)){
                registerImplType((Class<? extends Control>) type, implType);
            }            
        }
    }

    protected Constructor<?> getImplConstructor(Class<? extends Control> srcType){
        Class<?> type = srcType;
        
        while (true) {
            Constructor<?> result = controlMap.get(type);
            
            if (result != null){
                return result;
            }
            
            if (type == Control.class){
                return null;
            }
            
            type = type.getSuperclass();
        }
    }

    @Override
    public AbstractControl createImplementation(Control source) {
        try {
            Constructor<?> implConstructor = getImplConstructor(source.getClass());
            
            if (implConstructor != null){
                return (AbstractControl) implConstructor.newInstance(this, source);
            }
            
        } catch (Exception e) {
            throw new BridgeException(e);
        }
        
        throw new BridgeException("unknown control " + source.getClass());
    }

}
